package intervalmath

import (
	"fmt"
	"math"
)

// Interval defines the interval data type.
type Interval struct {
	// s is the start of the interval.
	S float64
	// e is the end of the interval.
	E float64
}

// New returns a new interval.
func New(s, e float64) (*Interval, error) {
	if s > e {
		return nil, fmt.Errorf("start(%g) > end(%g)", s, e)
	}
	return &Interval{s, e}, nil
}

func (i *Interval) String() string {
	return fmt.Sprintf("[%g, %g]", i.S, i.E)
}

// ContainsZero returns true if zero is contained in the interval.
func (i *Interval) ContainsZero() bool {
	return i.S <= 0 && i.E >= 0
}

// Positive returns true if the interval's S is positive.
func (i *Interval) Positive() bool {
	return i.S > 0
}

// Zero returns true if interval is [0,0]
func (i *Interval) Zero() bool {
	return i.S == 0 && i.E == 0
}

// Negative returns true if interval end is negative.
func (i *Interval) Negative() bool {
	return i.E < 0
}

func (i *Interval) Equals(o *Interval) bool {
	return i.S == o.S && i.E == o.E
}

// Add returns the sum of the two intervals a and b.
func Add(a, b *Interval) *Interval {
	return &Interval{a.S + b.S, a.E + b.E}
}

// Substract returns the difference between a and b.
func Substract(a, b *Interval) *Interval {
	return &Interval{a.S - b.E, a.E - b.S}
}

func min4(a, b, c, d float64) float64 {
	return math.Min(a, math.Min(b, math.Min(c, d)))
}

func max4(a, b, c, d float64) float64 {
	return math.Max(a, math.Max(b, math.Max(c, d)))
}

// Multiply returns the multiplication of the two intervals.
func Multiply(a, b *Interval) *Interval {
	if a.Positive() && b.Positive() || a.Negative() && b.Negative() {
		return &Interval{a.S * b.S, a.E * b.E}
	}
	if a.Positive() && b.Negative() {
		return &Interval{a.E * b.S, a.S * b.E}
	}
	if a.Negative() && b.Positive() {
		return &Interval{b.E * a.S, a.E * b.S}
	}
	ss, se, es, ee := a.S*b.S, a.S*b.E, a.E*b.S, a.E*b.E
	return &Interval{min4(ss, se, es, ee), max4(ss, se, es, ee)}
}

// Negate returns the negated interval.
// e.g. [1, 2] --> [-2, -1]
func (i *Interval) Negate() *Interval {
	return &Interval{-i.E, -i.S}
}

// Inverse returns the inverse of the interval defined as
// (a) i does not contain zero: [1/end, 1/start], nil
// (b) i contains zero: left interval (-inf, 1/start] and right interval (1/end, +inf).
// (c) i = [0, 0] -> (-inf, +inf), nil
func (i *Interval) Inverse() (left, right *Interval) {
	if !i.ContainsZero() {
		return &Interval{1.0 / i.E, 1.0 / i.S}, nil
	}
	if i.Zero() {
		return &Interval{math.Inf(-1), math.Inf(1)}, nil
	}
	return &Interval{math.Inf(-1), 1.0 / i.S}, &Interval{1.0 / i.E, math.Inf(1)}
}
